/////////////////////////////////////////////////////////////////////////////
// Name:        ax_menu_folder.cpp
// Purpose:     Modal folder selection menu class
// Author:      Julian Smart
// Modified by:
// Created:     2009-03-20
// RCS-ID:
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#include "wx/wx.h"
#include "wx/dir.h"
#include "wx/tokenzr.h"

#include "ax/ax_menu_folder.h"
#include "ax/ax_textinputdialog.h"
#include "ax/ax_utils.h"

// From dirctrlg.cpp
extern size_t wxGetAvailableDrives(wxArrayString &paths, wxArrayString &names, wxArrayInt &icon_ids);
extern bool wxIsDriveAvailable(const wxString& dirName);

IMPLEMENT_CLASS( AxModalFolderMenu, AxModalMenu )

AxModalFolderMenu::AxModalFolderMenu(AxMenuController* controller, AxMenuItem* item,
        const wxString& menuLabel,
        const wxString& description,
        const wxString& defaultPath,
        int style,
        const wxString& filter):
    AxModalMenu(controller, item)
{
    Init();

    m_path = defaultPath;
    m_style = style;
    m_filter = filter;

    AxMenu* menu = CreateSubMenu(wxEmptyString, wxEmptyString, menuLabel, description);
    SetMenu(menu);
}

void AxModalFolderMenu::Init()
{
    m_style = 0;
    m_filter = wxT("*");
}

AxMenu* AxModalFolderMenu::CreateSubMenu(const wxString& folder, const wxString& folderName, const wxString& label, const wxString& description)
{
    AxMenu* menu = new AxMenu;
    menu->SetLabel(label);
    menu->SetDescription(description);

    if (!folder.IsEmpty() && !IsFileSelector())
    {
        AxMenuItem* child = new AxMenuItem(_("Select ") + folderName, _("Select the folder ") + folder, wxEmptyString,
            new AxFolderMenuActivator(AxFolderMenuActivator::AxFolderCommandSelectThis, this, folder, folderName));
        child->GetActivator()->SetMenuItem(child);
        child->SetStyle(AxMenuStyleBold);
        menu->AddItem(child);
    }

    // Add an 'Up' item
    // but not at the top level
    if (!folder.IsEmpty())
    {
        menu->AddItem(new AxGoUpMenuItem);

        AxMenuItem* child = new AxMenuItem(_("New Folder"), _("Create a new folder."), wxT("NEW FOLDER"),
            new AxFolderMenuActivator(AxFolderMenuActivator::AxFolderCommandNewFolder, this, folder, folderName));
        child->GetActivator()->SetMenuItem(child);
        child->SetStyle(AxMenuStyleBold);
        menu->AddItem(child);
    }

    AxMenuItem* child = new AxMenuItem(_("Cancel"), _("Cancel this choice."), AxIdentifierCancel,
        new AxFolderMenuActivator(AxFolderMenuActivator::AxFolderCommandCancel, this));
    child->GetActivator()->SetMenuItem(child);
    child->SetStyle(AxMenuStyleBold);
    menu->AddItem(child);

    if (folder.IsEmpty())
    {
        wxArrayString paths, names;
        wxArrayInt icons;
        
        size_t i;
        size_t count = wxGetAvailableDrives(paths, names, icons);
        wxUnusedVar(count);
        
#ifdef __WXGTK20__
        wxString home = wxGetHomeDir();
        paths.Add(home);
        names.Add(_("Home directory"));
        icons.Add(1);

        home += wxT("/Desktop");
        paths.Add(home);
        names.Add(_("Desktop"));
        icons.Add(1);
#endif
        for (i = 0; i < paths.GetCount(); i++)
        {
            wxString label = names[i];
            wxString path = paths[i];
            wxString descr = _("Browse ") + path;
            wxString id = label;
            AxMenuItem* child = new AxMenuItem(label, descr, id,
                new AxFolderMenuActivator(AxFolderMenuActivator::AxFolderCommandFolder, this, path, path));
            child->GetActivator()->SetMenuItem(child);
            menu->AddItem(child);
        }
    }
    else
    {
        wxArrayString dirs;
        
        wxDir d;
        wxString eachFilename;
        bool showHidden = ShowHiddenFiles();
        
        wxLogNull log;
        d.Open(folder);
        
        if (d.IsOpened())
        {
            int style = wxDIR_DIRS;
            if (showHidden) style |= wxDIR_HIDDEN;
            if (d.GetFirst(& eachFilename, wxEmptyString, style))
            {
                do
                {
                    if ((eachFilename != wxT(".")) && (eachFilename != wxT("..")))
                    {
                        dirs.Add(eachFilename);
                    }
                }
                while (d.GetNext(&eachFilename));
            }
        }
        dirs.Sort();

        size_t i;
        for (i = 0; i < dirs.GetCount(); i++)
        {
            wxString folderName = dirs[i];
            wxString label = folderName;
            wxString folderPath = AxFileUtilities::AppendPaths(folder, folderName);
            wxString descr = _("Browse ") + folderPath;
            wxString id = label;

            AxMenuItem* child = new AxMenuItem(label, descr, id,
                new AxFolderMenuActivator(AxFolderMenuActivator::AxFolderCommandFolder, this, folderPath, folderName));
            child->GetActivator()->SetMenuItem(child);
            if (IsFileSelector())
                child->SetStyle(AxMenuStyleBold);
            menu->AddItem(child);
        }

        // Now do the filenames -- but only if we're allowed to
        if (IsFileSelector())
        {
            wxString eachFilename;
            d.Open(folder);
            wxArrayString filenames;
            
            if (d.IsOpened())
            {
                int style = wxDIR_FILES;
                if (showHidden) style |= wxDIR_HIDDEN;
                // Process each filter (ex: "*.jpg;*.jpeg")
                wxStringTokenizer strTok;
                wxString curFilter;
                strTok.SetString(m_filter, wxT(";"));
                while (strTok.HasMoreTokens())
                {
                    curFilter = strTok.GetNextToken();
                    if (d.GetFirst(& eachFilename, curFilter, style))
                    {
                        do
                        {
                            if ((eachFilename != wxT(".")) && (eachFilename != wxT("..")))
                            {
                                filenames.Add(eachFilename);
                            }
                        }
                        while (d.GetNext(& eachFilename));
                    }
                }
            }
            filenames.Sort();

            size_t i;
            for (i = 0; i < filenames.GetCount(); i++)
            {
                wxString fileName = filenames[i];
                wxString label = fileName;
                wxString filePath = AxFileUtilities::AppendPaths(folder, fileName);
                wxString descr = _("Examine ") + filePath;
                wxString id = label;

                AxMenuItem* child = new AxMenuItem(label, descr, id,
                    new AxFolderMenuActivator(AxFolderMenuActivator::AxFolderCommandFile, this, filePath, fileName));
                child->GetActivator()->SetMenuItem(child);
                menu->AddItem(child);

                // Build a static menu for the filename
                {
                    AxMenu* subMenu = new AxMenu;
                    subMenu->SetLabel(_("Select ") + fileName);
                    subMenu->SetDescription(_("Select the file") + filePath);

                    AxMenuItem* child2 = new AxMenuItem(_("Select ") + fileName,
                        _("Select the file ") + filePath, id,
                        new AxFolderMenuActivator(AxFolderMenuActivator::AxFolderCommandSelectThis, this, filePath, fileName));
                    child2->GetActivator()->SetMenuItem(child2);
                    child2->SetStyle(AxMenuStyleBold);
                    subMenu->AddItem(child2);

                    // Add an 'Up' item
                    subMenu->AddItem(new AxGoUpMenuItem);
                    
                    child2 = new AxMenuItem(_("Cancel"), _("Cancel this choice."), AxIdentifierCancel,
                        new AxFolderMenuActivator(AxFolderMenuActivator::AxFolderCommandCancel, this));
                    child2->GetActivator()->SetMenuItem(child2);
                    child2->SetStyle(AxMenuStyleBold);
                    subMenu->AddItem(child2);

                    child->SetSubMenu(subMenu);
                }
            }
        }
    }

    return menu;
}

/*
 * AxFolderMenuActivator implementation.
 * Ends the modal menu when the item is activated.
 */

IMPLEMENT_DYNAMIC_CLASS( AxFolderMenuActivator, AxActivator )

void AxFolderMenuActivator::Copy(const AxFolderMenuActivator& activator)
{
    AxActivator::Copy(activator);

    m_folderMenu = activator.m_folderMenu;
    m_command = activator.m_command;
    m_folderPath = activator.m_folderPath;
    m_folderName = activator.m_folderName;
}

AxMenu* AxFolderMenuActivator::Activate(AxMenuController* controller, AxMenuItem* item)
{
    if (m_command == AxFolderCommandFolder)
    {
        // Return menu with children
        wxString label = m_folderName;
        wxString descr = m_folderPath;
        if (!item->GetSubMenu())
        {
            AxMenu* menu = m_folderMenu->CreateSubMenu(m_folderPath, m_folderName, label, descr);
            item->SetSubMenu(menu);
        }
        return item->GetSubMenu();
    }
    else if (m_command == AxFolderCommandFile)
    {
        return item->GetSubMenu();
    }
    else if (m_command == AxFolderCommandSelectThis)
    {
        m_folderMenu->SetPath(m_folderPath);
        m_folderMenu->EndModal(AxIdentifierOK);
        return NULL;
    }
    else if (m_command == AxFolderCommandNewFolder)
    {
        AxTextInputDialog dialog(NULL, _("Enter a folder name"), _("New Folder"), wxEmptyString);
        if (dialog.ShowModal() == wxID_OK)
        {
            wxString newFolderName = dialog.GetValue();
            wxString folderPath = AxFileUtilities::AppendPaths(m_folderPath, newFolderName);
            folderPath = AxFileUtilities::MakeValidFilename(folderPath);
            if (!folderPath.IsEmpty() && !wxDirExists(folderPath))
            {
                if (!wxMkdir(folderPath))
                    return NULL;

                wxString label = newFolderName;
                wxString descr = _("Browse ") + folderPath;
                wxString id = label;
                
                AxMenuItem* child = new AxMenuItem(label, descr, id,
                    new AxFolderMenuActivator(AxFolderCommandFolder, m_folderMenu, folderPath, newFolderName));
                child->GetActivator()->SetMenuItem(child);
                if (m_folderMenu->IsFileSelector())
                    child->SetStyle(AxMenuStyleItalic);
                item->GetParent()->AddItem(child);

                controller->RefreshMenu(controller->GetCurrentMenu(), AxNavigationHintNone);
            }
        }
    }
    else if (m_command == AxFolderCommandCancel)
    {
        m_folderMenu->EndModal(AxIdentifierCancel);
        return NULL;
    }

    return NULL;
}

/*
 * AxFolderActivator implementation.
 * A ready-made menu item activator which sets a string
 * when the user chooses a folder.
 */

IMPLEMENT_DYNAMIC_CLASS( AxFolderActivator, AxAutoUpdateActivator )

AxFolderActivator::AxFolderActivator(const wxString& itemLabelSpec,
                                     const wxString& itemDescriptionSpec):
  AxAutoUpdateActivator(itemLabelSpec, itemDescriptionSpec)
{
}

// Operations

void AxFolderActivator::Copy(const AxFolderActivator& activator)
{
    AxAutoUpdateActivator::Copy(activator);
}

AxMenu* AxFolderActivator::Activate(AxMenuController* controller, AxMenuItem* item)
{
    AxFolderMenuItem* folderItem = GetFolderMenuItem();
    if (!folderItem)
        return NULL;

    wxString defaultFolder = folderItem->GetPath();

    AxModalFolderMenu menu(controller, item, folderItem->GetMenuLabel(), folderItem->GetDescription(), defaultFolder, folderItem->GetStyle(), folderItem->GetFilter());

    AxMenu* childMenu = FindChildMenu(controller, defaultFolder, menu.GetMenu());

    wxString id = menu.ShowModal(childMenu);

    if (id != AxIdentifierCancel)
    {
        folderItem->SetPath(menu.GetPath());

        UpdateItem(controller, item);
    }
    return NULL;
}

// Find the child menu for the default folder.
AxMenu* AxFolderActivator::FindChildMenu(AxMenuController* controller, const wxString& defaultFolder, AxMenu* menu)
{
    AxMenu* childMenu = NULL;
    if (!defaultFolder.IsEmpty())
    {
        wxString path2(defaultFolder);
        
        // Make sure all separators are as per the current platform
        path2.Replace(wxT("\\"), wxString(wxFILE_SEP_PATH));
        path2.Replace(wxT("/"), wxString(wxFILE_SEP_PATH));
        
        // Append a separator to foil bogus substring matching
        path2 += wxString(wxFILE_SEP_PATH);
        
        // In MSW or PM, case is not significant
#if defined(__WINDOWS__) || defined(__DOS__) || defined(__OS2__)
        path2.MakeLower();
#endif

        AxMenu* m = menu;
        while (m)
        {
            bool done = false;
            AxMenuItem* item = NULL;
            AxFolderMenuActivator* activator = NULL;
            size_t i;
            for (i = 0; i < m->GetCount(); i++)
            {
                AxMenuItem* item2 = m->GetItem(i);
                AxFolderMenuActivator* activator2 = wxDynamicCast(item2->GetActivator(), AxFolderMenuActivator);
                if (activator2 && !activator2->GetFolderPath().IsEmpty() &&
                    (activator2->GetCommand() == AxFolderMenuActivator::AxFolderCommandFolder || activator2->GetCommand() == AxFolderMenuActivator::AxFolderCommandFile))
                {
                    wxString childPath = activator2->GetFolderPath();
                    
                    if (!wxEndsWithPathSeparator(childPath))
                        childPath += wxString(wxFILE_SEP_PATH);
                    
                    // In MSW and PM, case is not significant
#if defined(__WINDOWS__) || defined(__DOS__) || defined(__OS2__)
                    childPath.MakeLower();
#endif
                    
                    if (childPath.length() <= path2.length())
                    {
                        wxString path3 = path2.Mid(0, childPath.length());
                        if (childPath == path3)
                        {
                            item = item2;
                            activator = activator2;
                            if (path3.length() == path2.length())
                                done = true;
                            else
                                done = false;
                            break;
                        }
                    }
                }
            }

            if (done)
            {
                if (item)
                {
                    childMenu = m;
                    int idx = m->GetItems().Index(item);
                    if (idx != -1)
                        m->SetCurrentSelection(idx);
                }
                m = NULL;
                break;
            }
            else if (item && activator)
            {
                AxMenu* newMenu = activator->Activate(controller, item);
                if (!item->GetSubMenu())
                    item->SetSubMenu(newMenu);
                m = newMenu;
            }
            else
                m = NULL;
        }
    }
    return childMenu;
}

wxString AxFolderActivator::CreateString(const wxString& spec, bool isLabel)
{
    AxFolderMenuItem* folderItem = GetFolderMenuItem();
    if (folderItem)
        return folderItem->CreateString(spec, isLabel);
    else
        return wxEmptyString;
}

AxFolderMenuItem* AxFolderActivator::GetFolderMenuItem() const
{
    return wxDynamicCast(GetMenuItem(), AxFolderMenuItem);
}

/*
 * AxModalFileMenu implementation.
 */

IMPLEMENT_CLASS( AxModalFileMenu, AxModalFolderMenu )

AxModalFileMenu::AxModalFileMenu(AxMenuController* controller, AxMenuItem* item,
        const wxString& menuLabel,
        const wxString& description,
        const wxString& defaultPath,
        int style,
        const wxString& filter):
            AxModalFolderMenu(controller, item, menuLabel, description, defaultPath, style|axFOLDER_MENU_STYLE_FILE_SELECTOR,
                  filter)
{
}

/*
 * AxFolderMenuItem implementation.
 */

IMPLEMENT_DYNAMIC_CLASS( AxFolderMenuItem, AxMenuItem )

AxFolderMenuItem::AxFolderMenuItem(
        const wxString& label, const wxString& description,
        const wxString& id,
        const wxString& path,
        const wxString& filter,
        int style)
{
    Create(label, description, id, path, filter, style);
}

bool AxFolderMenuItem::Create(
        const wxString& label, const wxString& description,
        const wxString& id,
        const wxString& path,
        const wxString& filter,
        int style)
{
    SetLabel(label);
    SetDescription(description);
    SetId(id);
    SetDynamic();

    m_path = path;
    m_filter = filter;
    m_style = style;

    wxString menuLabel, menuDescription;
    if (style&axFOLDER_MENU_STYLE_FILE_SELECTOR)
    {
        m_menuLabel = _("File Selection");
        m_menuDescription = _("File Selection");
        SetKind(_("file selector"));
    }
    else
    {
        m_menuLabel = _("Folder Selection");
        m_menuDescription = _("Folder Selection");
        SetKind(_("folder selector"));
    }

    AxFolderActivator* activator = new AxFolderActivator(label, description);
    SetActivator(activator);

    return true;
}

void AxFolderMenuItem::Copy(const AxFolderMenuItem& item)
{
    AxMenuItem::Copy(item);

    m_path = item.m_path;
}

// Set path
void AxFolderMenuItem::SetPath(const wxString& path)
{
    m_path = path;

    TransferDataFromMenu();
}

wxString AxFolderMenuItem::CreateString(const wxString& spec, bool WXUNUSED(isLabel))
{
    wxString value = GetPath();
    wxString str = spec;

    wxString folderName = wxFileNameFromPath(value);
    
    if (value.IsEmpty())
        value = _("none");
    if (folderName.IsEmpty())
        folderName = _("none");
        
    str.Replace(wxT("%FOLDER%"), value);
    str.Replace(wxT("%FOLDERNAME%"), folderName);
    str.Replace(wxT("%FILE%"), value);
    str.Replace(wxT("%FILENAME%"), folderName);

    return str;
}

/*
 * AxFileMenuItem implementation.
 */

IMPLEMENT_DYNAMIC_CLASS( AxFileMenuItem, AxFolderMenuItem )

AxFileMenuItem::AxFileMenuItem(const wxString& label, const wxString& description,
        const wxString& id,
        const wxString path,
        const wxString& filter,
        int style)
{
    Create(label, description, id, path, filter, style|axFOLDER_MENU_STYLE_FILE_SELECTOR);
}

bool AxFileMenuItem::Create(const wxString& label, const wxString& description,
        const wxString& id,
        const wxString path,
        const wxString& filter,
        int style)
{
    return AxFolderMenuItem::Create(label, description, id,
        path, filter, style|axFOLDER_MENU_STYLE_FILE_SELECTOR);
}

IMPLEMENT_DYNAMIC_CLASS( AxFolderValidator, AxValidator )

// Override to pass a value from the UI to a data structure
bool AxFolderValidator::TransferDataFromMenu()
{
    AxFolderMenuItem* folderItem = wxDynamicCast(GetMenuItem(), AxFolderMenuItem);
    if (folderItem)
    {
        if (m_stringPtr)
            * m_stringPtr = folderItem->GetPath();
    }

    return true;
}

// Override to pass a value from a data structure to the UI
bool AxFolderValidator::TransferDataToMenu()
{
    AxFolderMenuItem* folderItem = wxDynamicCast(GetMenuItem(), AxFolderMenuItem);
    if (folderItem)
    {
        if (m_stringPtr)
            folderItem->SetPath(* m_stringPtr);
    }

    return true;
}
